﻿//////////////////////////////////////////////////
// Copyright 2007, Clutch, Inc.
// Author: Jeff Cesnik
//////////////////////////////////////////////////

using System;
using System.Collections;
using System.Security.Cryptography;
using System.Text;

namespace Lab620.WcfRadiusService.Clutch
{
    public static class PapAuthenticator
    {
        public static bool Authenticate(byte[] sharedSecret, byte[] rqAuthenticator,
                                        byte[] requestPassword, string clearPassword)
        {
            // PAPAuthenticator will encrypt the dbPassword value and compare it to the rpPassword value

            int passwordBuffLength;
            var authData = new byte[16];
            var currentPasswordBlock = new byte[16];

            // reduce dbPassword to byte array, pad to 16 byte boundary
            int passwordByteLength = passwordBuffLength = Encoding.ASCII.GetByteCount(clearPassword);

            if ((passwordByteLength%16) != 0)
                passwordBuffLength += 16 - (passwordByteLength%16);

            var passwordBuffer = new byte[passwordBuffLength];
            Buffer.BlockCopy(Encoding.ASCII.GetBytes(clearPassword), 0, passwordBuffer, 0, passwordByteLength);
            int passwordBlocks = passwordBuffLength/16;

            // for the first round, init authData to request authenticator value
            Buffer.BlockCopy(rqAuthenticator, 0, authData, 0, 16);

            for (int i = 0; i < passwordBlocks; i++)
            {
                // copy the password block to cipherData
                Buffer.BlockCopy(passwordBuffer, i*16, currentPasswordBlock, 0, 16);
                // encrypt in place
                PAPEncryptBlock(sharedSecret, authData, currentPasswordBlock);


                // test encrypted password against the request password
                for (int j = 0; j < 16; j++)
                {
                    if (currentPasswordBlock[j] != requestPassword[(i*16) + j])
                        return false;
                }

                // copy cipherData to authData for next round
                Buffer.BlockCopy(currentPasswordBlock, 0, authData, 0, 16);
            }

            return true;
        }


        public static void PAPEncryptBlock(byte[] sharedSecret, byte[] authData, byte[] cipherData)
        {
            MD5 md5 = new MD5CryptoServiceProvider();
            var baTemp = new byte[sharedSecret.Length + 16];
            Buffer.BlockCopy(sharedSecret, 0, baTemp, 0, sharedSecret.Length);
            Buffer.BlockCopy(authData, 0, baTemp, sharedSecret.Length, 16);

            var biData = new BitArray(cipherData);
            biData.Xor(new BitArray(md5.ComputeHash(baTemp))).CopyTo(cipherData, 0);
        }
    }
}